package org.fglxy.mockserver.netty;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.GlobalEventExecutor;

@Component
public class WebSocketServer {
	
	@Value("${websocket.port}")
	private int port;
	
	private Channel channel;
	
	private EventLoopGroup bossGroup;
	private EventLoopGroup workerGroup;
	
	private ConcurrentHashMap<String, ChannelGroup> eventMap = new ConcurrentHashMap<>();
	private ConcurrentHashMap<String, CopyOnWriteArrayList<String>> eventList = new ConcurrentHashMap<>();
	
	public static WebSocketServer Instance;
	
	@PostConstruct
	public void start() throws InterruptedException {
		this.bossGroup = new NioEventLoopGroup();
		this.workerGroup = new NioEventLoopGroup();
		
		ServerBootstrap boot = new ServerBootstrap();
		boot.option(ChannelOption.SO_BACKLOG, 1024);
		
		Instance = this;
		
		boot.group(bossGroup, workerGroup)
			.channel(NioServerSocketChannel.class)
			.localAddress(this.port)
			.childHandler(new ChannelInitializer<SocketChannel>() {

				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new HttpServerCodec())
						.addLast(new ChunkedWriteHandler())
						.addLast(new HttpObjectAggregator(8192))
						.addLast(new WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10))
						.addLast(new WebSocketCommandHandler(Instance));
				}
				
			});
		final ChannelFuture future = boot.bind().sync();
		this.channel = future.channel();
	}
	
	public void release() {
		if (this.bossGroup != null && !this.bossGroup.isShutdown()) {
			this.bossGroup.shutdownGracefully();
		}
		if (this.workerGroup != null && !this.workerGroup.isShutdown()) {
			this.workerGroup.shutdownGracefully();
		}
	}
	
	public void registMockChangeEvent(String mockId, Channel channel) {
		ChannelGroup channelList = this.eventMap.put(mockId, new DefaultChannelGroup(GlobalEventExecutor.INSTANCE));
		if (channelList.contains(channel)) {
			return;
		}
		channelList.add(channel);
		CopyOnWriteArrayList<String> newEvents = new CopyOnWriteArrayList<>();
		CopyOnWriteArrayList<String> events = this.eventList.putIfAbsent(channel.id().asLongText(), newEvents);
		events = events == null ? newEvents : events;
		events.add(mockId);
	}
	
	public void clearEvent(Channel channel) {
		String id = channel.id().asLongText();
		if (!this.eventList.containsKey(id)) {
			return;
		}
		for (String event : this.eventList.get(id)) {
			this.eventMap.get(event).remove(channel);
		}
		this.eventList.remove(id);
	}
	
	public void sendMsg(String mockId, String msg) {
		if (this.eventMap.containsKey(mockId)) {
			this.eventMap.get(mockId).writeAndFlush(msg);
		}
	}
	
	public Channel getChannel() {
		return this.channel;
	}
}
